/*
 * Created on 2008-8-5
 *
 * TODO
 */
package jacky.lanlan.song.junitx.v4.conc;

import java.lang.reflect.Field;
import java.util.List;

import org.junit.runner.Description;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;
import org.junit.runner.notification.RunNotifier;

/**
 * JUnit4规定的第一个监听器 <code>ResultListener</code> 的替换器。
 * <p>
 * 由于并发的关系，如果测试出现故障， 原始的 <code>ResultListener</code> 将监听到很多线程报告的故障，而原本的故障却只有一个，
 * 这样会导致一个测试出现很多次故障，给测试结果带来混淆。<br/>
 * 所以，需要对原始的 <code>ResultListener</code> 进行修改，使其适应并发测试的情况。
 * <p>
 * 这个替换器的策略是使用Wrapper模式包装原始的 <code>ResultListener</code> ，加入支持并发测试的特性，然后替换原来的。
 * @author Jacky.Song
 */
class ResultListenerReplacer {
	
	private static List<RunListener> cachedListenerList;

	private static RunListener cachedResultListenerWrapper;
	
	/**
	 * 通过反射，得到 <code>RunNotifier</code> 中的监听器列表。
	 * （<code>RunNotifier</code> 的监听器列表是private的，只能通过反射获得。）
	 * <p>
	 * 因为 <code>RunNotifier</code> 贯穿整个JUnit测试过程，相当于一个Singeton，所以不需要每次测试都反射，因此方法使用了缓存。
	 * @param notifier RunNotifier
	 * @return notifier 中的监听器列表
	 */
	private static List<RunListener> getLisenters(RunNotifier notifier){
		if (cachedListenerList == null) {
			
			Field f=null;
			try {
				f = RunNotifier.class.getDeclaredField("fListeners");
				f.setAccessible(true);
				cachedListenerList = (List) f.get(notifier);
			}
			catch (Exception e) {e.printStackTrace();}
		}
		return cachedListenerList;
	}
	
	/**
	 * 创建 <code>ResultListenerWrapper</code> 。
	 * <p>
	 * 和 <code>RunNotifier</code> 一样，也可以缓存。
	 * @param listeners 监听器列表
	 * @return ResultListenerWrapper
	 */
	private static RunListener createResultListenerWrapper(List<RunListener> listeners) {
		// 得到ResultListener，这个类是JUnit4规定的第一个监听器，负责收集测试数据
		final RunListener resultListener=listeners.get(0);
		// 缓存
		if(cachedResultListenerWrapper==null) {
			cachedResultListenerWrapper=new ResultListenerWrapper(resultListener) ;
		}
		return cachedResultListenerWrapper;
	}
	
	/**
	 * 使用Wrapper模式包装的ResultListener，改变了原来ResultListener的一些行为。
	 * @author Jacky.Song
	 */
	private static class ResultListenerWrapper extends RunListener{
		private RunListener original;// 原始ResultListener

		public ResultListenerWrapper(RunListener resultListener) {
			this.original=resultListener;
		}
		
		/*
		 * 因为是并发测试，所以一个测试中的故障会反复出现(多个线程反复调用一个测试方法，所以可能反复出现这个故障)，
		 * 这时只应该记录一个故障，否则最后的测试结果会出现不止一个故障，这样会让人混淆。
		 * 
		 * 基于以上原因，需要一个做个标记以限制每个测试的故障数。
		 */
		private boolean failureAdded;
		
		@Override
		public void testFailure(Failure failure) throws Exception {
			if(!failureAdded) {
				original.testFailure(failure);
				failureAdded=true;
			}
		}
		@Override
		public void testFinished(Description description) throws Exception {
			original.testFinished(description);
		}
		@Override
		public void testIgnored(Description description) throws Exception {
			original.testIgnored(description);
		}
		@Override
		public void testRunFinished(Result result) throws Exception {
			original.testRunFinished(result);
		}
		@Override
		public void testRunStarted(Description description) throws Exception {
			original.testRunStarted(description);
		}
		@Override
		public void testStarted(Description description) throws Exception {
			failureAdded=false;// 在每个测试开始前，重置标记，以免影响到其他的测试，而由于JUnit是单线程运行，所以不会有并发问题
			original.testStarted(description);
		}
	}
	
	/**
	 * 替换 <code>RunNotifier</code> 中的 <code>ResultListener</code> 监听器。
	 * @param notifier RunNotifier
	 */
	public static void replace(RunNotifier notifier) {
		getLisenters(notifier);

		/*
		 * 我需要改变ResultListener的行为，但它事先已经被添加到监听器列表中了，所以要么用方法拦截，要么用Wrapper。
		 * 考虑到性能问题(Listener的调用很频繁，每次assertXXX()的时候，都会触发Listener)，这里使用Wrapper。
		 */
		
		RunListener wrapper = createResultListenerWrapper(cachedListenerList);
		
		/*
		 * 偷梁换柱，用这个Listener替换ResultListener，这样就透明的影响了JUnit
		 */
		cachedListenerList.remove(0);// 删除原来真正的ResultListener
		cachedListenerList.add(0, wrapper);// 把第一个位置换成WrapperResultListener，搞定
	}
	
}
